#include<vector>
#include<string>
#include<iostream>
#include"NumArray.h"
#include"LargeNumber.h"
#include"ProxyNum.h"

using namespace std;


unsigned const MAX_SMALL_NUM = size_t(-1) / 2;

NumArray::NumArray() : size_(0), number_(0) {}

NumArray::NumArray(std::string const & num) {
    size_ = num.size();
    number_ = new char[size_];
    for(size_t i = 0; i < size_; ++i) {
        number_[size_ - 1 - i] = num[i] - '0';
    }
    if(size_ < 10) {
        convertToSmallNum();
    }
}

NumArray::NumArray(unsigned long const & num) {
    if(num < MAX_SMALL_NUM) {
        number_ = 0;
        number_ += num;
        size_ = 0;
    } else {
        convertToLargeNum(num);
    }
}

NumArray::NumArray(NumArray const & copy): size_(copy.size_) {
    if(size_ == 0) {
        number_ = copy.number_;
    } else {
        number_ = new char[size_];
        for(size_t i = 0; i < size_; ++i) {
            number_[i] = copy.number_[i];
        }
    }
}

NumArray::~NumArray() {
    if(size_ != 0) {
        delete[] number_;
        size_ = 0;
    }
    number_ = 0;
}

NumArray & NumArray::operator=(NumArray const & right) {
    NumArray(right).swap(*this);
    return *this;
}

int NumArray::operator[] (size_t index) const {
    if(index < size()) {
        if(size_ == 0) {
            return int(number_);
        }
        return number_[index];
    }
    return -1;
}

ProxyNum NumArray::operator[] (size_t index) {
    return ProxyNum(this, index);
}

void NumArray::convertToLargeNum(unsigned long long new_value) {
    if(size_ == 0) {
        vector<char> array;
        do {
            array.push_back(new_value % 10);
            new_value /= 10;
        } while(new_value > 9);
        array.push_back(new_value);
        size_ = array.size();
        number_ = new char[size_];
        for(size_t i = 0; i < size_; ++i) {
            number_[i] = array[i];
        }
    }
}

void NumArray::convertToSmallNum() {
    if(size_ != 0) {
        size_t i = 0;
        unsigned current_int = 0;
        while (i != size_ && unsigned(current_int * 10 + number_[size_ - i - 1]) < MAX_SMALL_NUM) {
            current_int *= 10;
            current_int += number_[size_ - i - 1];
            if(current_int == 0) {
                ++current_int;
            }
            ++i;
        }
        if(i == size_) {
            size_ = 0;
            delete[] number_;
            number_ = 0;
            number_ += current_int;
        }
    }
}

bool NumArray::isSmall() const {
    if(size_ == 0)
        return true;
    return false;
}

size_t NumArray::size() const {
    if(size_ == 0)
        return 1;
    return size_;
}

void NumArray::swap(NumArray & right) {
    std::swap(size_, right.size_);
    std::swap(number_, right.number_);
}

void NumArray::resize(size_t new_size) {
    if(new_size != size_) {
        if(isSmall()) {
            convertToLargeNum(int(number_));
        }
        size_t min_size = std::min(new_size, size_);
        char * new_number_ = new char[new_size];
        for(size_t i = 0; i < min_size; ++i) {
            new_number_[i] = number_[i];
        }
        for(size_t i = min_size; i < new_size; ++i) {
            new_number_[i] = 0;
        }
        delete[] number_;
        number_ = new_number_;
        size_ = new_size;
    }
}

void NumArray::normalize() {
    if(!isSmall()) {
        char * end = number_ + size() - 1;
        while(*end == 0 && end != (number_ - 1)) {
            --end;
        }
        size_t new_size = (end == number_) ? 1 : ( end - number_ + 1 );
        resize(new_size);
        if(new_size < 10) {
            convertToSmallNum();
        }
    } else {
        convertToLargeNum(int(number_));
    }
}

std::ostream & operator <<(std::ostream & out, NumArray const & num) {
    if(num.isSmall()) {
        out << int(num.number_);
    } else {
        size_t size = num.size();
        for(size_t i = 0; i < size; ++i) {
            out << char(num.number_[size - i - 1] + '0');
        }
    }
    return out;
}

istream & operator>> (istream & in, NumArray & num) {
    string str;
    in >> str;
    num = NumArray(str);
    return in;
}
